package com.orange.wwwapi.service.carpark;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.DigestUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.orange.core.exception.DuplicateException;
import com.orange.core.mapper.user.UserMapper;
import com.orange.core.pojo.entity.carpark.CarParkOrder;
import com.orange.core.pojo.entity.credit.CreditFlow;
import com.orange.core.pojo.entity.user.User;
import com.orange.core.pojo.entity.user.UserCar;
import com.orange.core.pojo.enums.EErrorCode;
import com.orange.core.pojo.enums.EFlowBusinessType;
import com.orange.core.pojo.enums.EFlowType;
import com.orange.core.service.carpark.CarParkOrderService;
import com.orange.core.service.config.ConfigService;
import com.orange.core.service.credit.CreditFlowService;
import com.orange.core.util.Constants;
import com.orange.wwwapi.client.YmiotCarParkClient;
import com.orange.wwwapi.config.YmiotCarParkProperty;
import com.orange.wwwapi.pojo.req.carpark.songjiang.CarNoCouponReq;
import com.orange.wwwapi.pojo.req.carpark.songjiang.CarNoOrderFeeReq;
import com.orange.wwwapi.pojo.req.carpark.songjiang.CouponSolutionReq;
import com.orange.wwwapi.pojo.rsp.carpark.songjiang.CarNoCouponRsp;
import com.orange.wwwapi.pojo.rsp.carpark.songjiang.CarNoOrderFeeRsp;
import com.orange.wwwapi.pojo.rsp.carpark.songjiang.CouponSolutionRsp;
import com.orange.wwwapi.pojo.rsp.user.UserCarParkFeeRsp;
import com.orange.wwwapi.service.user.UserCarService;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

@Slf4j
@RequiredArgsConstructor
@ConditionalOnBean(YmiotCarParkProperty.class)
@Service
public class YmiotCarParkService {

    private static final String CAR_PARK_FEE_KEY = "car_park_fee:%s:%s";

    private final UserCarService userCarService;
    private final CarParkOrderService carParkOrderService;
    private final ConfigService configService;
    private final CreditFlowService creditFlowService;
    private final UserMapper userMapper;
    private final YmiotCarParkClient ymiotCarParkClient;
    private final YmiotCarParkProperty ymiotCarParkProperty;
    private final StringRedisTemplate redisTemplate;
    private final ObjectMapper objectMapper;

    @SneakyThrows
    public UserCarParkFeeRsp getCarParkFee(String plateNo, int userId) {
        CarNoOrderFeeRsp carNoOrderFee = getCarNoOrderFee(plateNo);
        Assert.notNull(carNoOrderFee, "查询停车费用失败");
        Assert.equals(carNoOrderFee.getCode(), 1, "查询停车费用失败，" + carNoOrderFee.getMsg());

        CarNoOrderFeeRsp.DataDTO data = carNoOrderFee.getData();

        CarParkOrder carParkOrder = carParkOrderService.lambdaQuery()
                .eq(CarParkOrder::getParkOrderNo, data.getOrderNo())
                .one();
        Assert.isNull(carParkOrder, () -> new DuplicateException(EErrorCode.DUPLICATE_EXCHANGE_CAR_COUPON.getMessage()));

        BigDecimal amount = new BigDecimal(data.getTotalAmount());

        LocalDateTime entryTime = LocalDateTime.parse(data.getEnterTime(), Constants.DEFAULT_DATE_TIME_FORMATTER);
        Duration duration = LocalDateTimeUtil.between(entryTime, LocalDateTime.now());

        BigDecimal deductibleAmount = BigDecimal.ZERO;
        int deductibleCredit = 0;
        String couponSolutionNo = null;

        if (amount.compareTo(BigDecimal.ZERO) > 0) {
            // 获取用户信息
            User user = userMapper.selectById(userId);

            // 获取积分兑换金额比例
            BigDecimal creditParkAmountRate = configService.getCreditParkAmountRate();

            // 计算用户积分可以兑换的最大金额
            BigDecimal maxDeductibleAmount = new BigDecimal(user.getCredit()).divide(creditParkAmountRate, 0, RoundingMode.FLOOR);

            // 获取第三方优惠方案
            List<CouponSolutionRsp.DataDTO> couponSolutions = getCouponSolution();
            if (CollUtil.isNotEmpty(couponSolutions)) {
                // 过滤有效的优惠方案（剩余数量大于0）
                List<CouponSolutionRsp.DataDTO> validSolutions = couponSolutions.stream()
                        .filter(d -> d.getCouponSolutionRemainingCount() > 0)
                        .sorted(Comparator.comparingDouble(CouponSolutionRsp.DataDTO::getCouponSolutionValue).reversed())
                        .toList();

                // 找到最适合的优惠方案（金额优惠）
                CouponSolutionRsp.DataDTO bestSolution = null;
                for (CouponSolutionRsp.DataDTO solution : validSolutions) {
                    // 检查优惠方式是否为金额优惠
                    if ("减免金额".equals(solution.getCouponTypeName()) && "大橘商城积分".equals(solution.getBusinessManagerNickName())) {
                        BigDecimal solutionAmount = BigDecimal.valueOf(solution.getCouponSolutionValue());

                        // 如果优惠金额不超过停车费总额，且不超过用户积分可兑换的最大金额
                        if (solutionAmount.compareTo(amount) <= 0 && solutionAmount.compareTo(maxDeductibleAmount) <= 0) {
                            bestSolution = solution;
                            break;
                        }
                    }
                }

                if (bestSolution != null) {
                    deductibleAmount = BigDecimal.valueOf(bestSolution.getCouponSolutionValue());
                    couponSolutionNo = bestSolution.getCouponsolutionNo();
                }
            }

            // 计算需要扣除的积分
            if (deductibleAmount.compareTo(BigDecimal.ZERO) > 0) {
                deductibleCredit = deductibleAmount.multiply(creditParkAmountRate).intValue();
            }
        }

        UserCarParkFeeRsp rsp = UserCarParkFeeRsp.builder()
                .userId(userId)
                .plateNo(plateNo)
                .parkName(ymiotCarParkProperty.getParkName())
                .parkDuration(DurationFormatUtils.formatDuration(duration.toMillis(), "HH时mm分钟ss秒"))
                .parkOrderNo(data.getOrderNo())
                .entryTime(data.getEnterTime())
                .totalAmount(amount.stripTrailingZeros())
                .deductibleCredit(deductibleCredit)
                .deductibleAmount(deductibleAmount)
                .couponSolutionNo(couponSolutionNo)
                .deductAll(deductibleAmount.compareTo(amount) >= 0)
                .build();

        String key = String.format(CAR_PARK_FEE_KEY, userId, plateNo);
        redisTemplate.opsForValue().set(key, objectMapper.writeValueAsString(rsp));
        redisTemplate.expire(key, 10, TimeUnit.MINUTES);

        return rsp;
    }

    @SneakyThrows
    @Transactional
    public String exchangeCoupon(String plateNo, int userId) {
        String key = String.format(CAR_PARK_FEE_KEY, userId, plateNo);
        String str = redisTemplate.opsForValue().get(key);
        Assert.notBlank(str, "停车信息已超时，请重新查询");

        UserCarParkFeeRsp userCarParkFeeRsp = objectMapper.readValue(str, UserCarParkFeeRsp.class);
        User user = userMapper.selectById(userId);
        Assert.isTrue(userCarParkFeeRsp.getDeductibleCredit() <= user.getCredit());

        Assert.isTrue(userMapper.deductCredit(userId, userCarParkFeeRsp.getDeductibleCredit()), "扣减积分失败");

        UserCar userCar = userCarService.lambdaQuery()
                .eq(UserCar::getUserId, userId)
                .eq(UserCar::getPlateNo, plateNo)
                .one();

        CarParkOrder carParkOrder = new CarParkOrder()
                .setUserId(userId)
                .setUserCarId(userCar.getId())
                .setPlateNo(plateNo)
                .setParkName(ymiotCarParkProperty.getParkName())
                .setParkOrderNo(userCarParkFeeRsp.getParkOrderNo())
                .setEntryTime(LocalDateTimeUtil.parse(userCarParkFeeRsp.getEntryTime(), DatePattern.NORM_DATETIME_FORMATTER))
                .setParkDuration(userCarParkFeeRsp.getParkDuration())
                .setTotalAmount(userCarParkFeeRsp.getTotalAmount())
                .setPaidCredit(userCarParkFeeRsp.getDeductibleCredit())
                .setDeductAmount(userCarParkFeeRsp.getDeductibleAmount())
                .setCouponSolutionNo(userCarParkFeeRsp.getCouponSolutionNo());
        carParkOrderService.save(carParkOrder);

        creditFlowService.save(new CreditFlow()
                .setUserId(userId)
                .setBusinessId(carParkOrder.getId())
                .setBusinessType(EFlowBusinessType.PARKING_FEE.getValue())
                .setFlowType(EFlowType.CONSUME.getValue())
                .setCredit(-userCarParkFeeRsp.getDeductibleCredit()));

        CarNoCouponRsp carNoCouponRsp = carNoCoupon(plateNo, userCarParkFeeRsp.getCouponSolutionNo());
        Assert.notNull(carNoCouponRsp, "积分兑换优惠券失败");
        Assert.equals(carNoCouponRsp.getCode(), 1, "积分兑换优惠券失败，" + carNoCouponRsp.getMsg());

        redisTemplate.delete(key);

        return carNoCouponRsp.getData().getCouponKey();
    }

    @SneakyThrows
    private List<CouponSolutionRsp.DataDTO> getCouponSolution() {
        List<CouponSolutionRsp.DataDTO> couponSolutions = new ArrayList<>();
        List<CouponSolutionRsp.DataDTO> data;
        int indexId = 0;
        do {
            CouponSolutionReq req = CouponSolutionReq.builder()
                    .appid(ymiotCarParkProperty.getAppid())
                    .parkKey(ymiotCarParkProperty.getParkKey())
                    .rand(String.valueOf(RandomUtil.randomFloat(Float.MAX_VALUE)))
                    .version(ymiotCarParkProperty.getVersion())
                    .indexId("" + indexId)
                    .size("20")
                    .build();
            req.setSign(generateSign(req));
            CouponSolutionRsp couponSolutionRsp = ymiotCarParkClient.getCouponSolution(req);
            data = couponSolutionRsp.getData();
            couponSolutions.addAll(data);
            indexId += 20;
        } while (CollUtil.isNotEmpty(data));
        return couponSolutions;
    }

    private CarNoOrderFeeRsp getCarNoOrderFee(String carNo) {
        CarNoOrderFeeReq req = CarNoOrderFeeReq.builder()
                .appid(ymiotCarParkProperty.getAppid())
                .parkKey(ymiotCarParkProperty.getParkKey())
                .rand(String.valueOf(RandomUtil.randomFloat(Float.MAX_VALUE)))
                .version(ymiotCarParkProperty.getVersion())
                .carNo(carNo)
                .freeMinutes("0")
                .build();
        req.setSign(generateSign(req));
        return ymiotCarParkClient.getCarNoOrderFee(req);
    }

    private CarNoCouponRsp carNoCoupon(String carNo, String couponSolutionNo) {
        CarNoCouponReq req = CarNoCouponReq.builder()
                .appid(ymiotCarParkProperty.getAppid())
                .parkKey(ymiotCarParkProperty.getParkKey())
                .rand(String.valueOf(RandomUtil.randomFloat(Float.MAX_VALUE)))
                .version(ymiotCarParkProperty.getVersion())
                .carNo(carNo)
                .couponsolutionNo(couponSolutionNo)
                .build();
        req.setSign(generateSign(req));
        return ymiotCarParkClient.carNoCoupon(req);
    }

    @SneakyThrows
    private String generateSign(Object obj) {
        Assert.notNull(obj);
        Class<?> clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        Map<String, String> signMap = new TreeMap<>();
        for (Field field : fields) {
            field.setAccessible(true);
            Object value = field.get(obj);
            if (value == null) {
                continue;
            }
            signMap.put(field.getName(), value.toString());
        }
        Assert.notEmpty(signMap);

        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> e : signMap.entrySet()) {
            sb.append(e.getKey()).append("=").append(e.getValue()).append("&");
        }
        sb.append(ymiotCarParkProperty.getAppSecret());
        log.info("待签名字符串：{}", sb);

        String sign = DigestUtil.md5Hex(sb.toString()).toUpperCase();
        log.info("签名字符串：{}", sign);
        return sign;
    }
}
